/**
 * This file is part of 1genia trampoline
 * Copyright (C) 2007 1genia (contact@1genia.com)
 *
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; version 3 of the License. 
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details. 
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; see the file COPYING.TXT.  If not,
 * write to the Free Software Foundation, Inc., 51 Franklin Street,
 * Fifth Floor, Boston, MA 02110-1301, USA. 
 **/
package com.genia.toolbox.portlet.editor;

import java.io.File;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.rmi.server.UID;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.FileSystemResource;
import org.springframework.web.servlet.mvc.Controller;

import com.genia.toolbox.basics.editor.AbstractApplication;
import com.genia.toolbox.basics.exception.BundledException;
import com.genia.toolbox.basics.i18n.impl.I18nMessageImpl;
import com.genia.toolbox.portlet.editor.gui.PortletEditorGUI;
import com.genia.toolbox.portlet.editor.message.Messages;
import com.genia.toolbox.portlet.editor.model.bean.PortletInitialSettings;
import com.genia.toolbox.portlet.editor.model.document.impl.DocumentModel;
import com.genia.toolbox.portlet.editor.model.portlet.PortletType;
import com.genia.toolbox.portlet.editor.model.portlet.factory.PortletModelFactory;
import com.genia.toolbox.portlet.editor.model.portlet.impl.PortletModel;
import com.genia.toolbox.portlet.editor.visitor.HolesPortletVisitor;
import com.genia.toolbox.portlet.editor.visitor.TemplatePortletVisitor;
import com.genia.toolbox.web.portlet.bean.Portlet;
import com.genia.toolbox.web.portlet.bean.impl.AbstractPortlet;
import com.genia.toolbox.web.portlet.bean.impl.ContainerPortlet;
import com.genia.toolbox.web.portlet.bean.impl.DispatcherPortlet;
import com.genia.toolbox.web.portlet.bean.impl.HolePortlet;
import com.genia.toolbox.web.portlet.bean.impl.LinkedPortlet;
import com.genia.toolbox.web.portlet.bean.impl.SimplePortlet;
import com.genia.toolbox.web.portlet.bean.impl.TemplatePortlet;
import com.genia.toolbox.web.portlet.description.ContainerPortletDescription;
import com.genia.toolbox.web.portlet.description.PortletDescription;
import com.genia.toolbox.web.portlet.description.SimplePortletDescription;
import com.genia.toolbox.web.portlet.description.VariableDescription;
import com.genia.toolbox.web.portlet.description.ContainerPortletDescription.LayoutDirection;
import com.genia.toolbox.web.portlet.description.impl.SimplePortletDescriptionImpl;
import com.genia.toolbox.web.portlet.visitor.impl.SpringWriterPortletVisitor;

/**
 * The application.
 */
public class PortletEditorApplication
    extends AbstractApplication<PortletEditorController, DocumentModel, PortletModel, PortletInitialSettings>
{
  /**
   * The model factory.
   */
  private PortletModelFactory modelFactory = null;

  /**
   * The container portlet descriptions.
   */
  private Map<String, ContainerPortletDescription> containerDescriptions = null;

  /**
   * The simple portlet descriptions.
   */
  private Map<String, SimplePortletDescription> simpleDescriptions = null;

  /**
   * The templates.
   */
  private Map<String, Portlet> templates = null;

  /**
   * The portlets.
   */
  private Map<String, Portlet> portlets = null;



  /**
   * Constructor (by default the GUI will be dislayed).
   */
  public PortletEditorApplication()
  {
    super();
  }



  /**
   * Perform tasks while initialising.
   * 
   * @throws BundledException
   *           If there is an initialisation error.
   */
  @SuppressWarnings("unchecked")
  @Override
  public void doInitialise()
      throws BundledException
  {
    this.containerDescriptions = new HashMap<String, ContainerPortletDescription>();
    this.simpleDescriptions = this.getSimplePortletDescriptions();
    this.templates = new HashMap<String, Portlet>();
    this.portlets = new HashMap<String, Portlet>();

    Map<String, ContainerPortletDescription> allContainerDescriptions = this.getApplicationContext().getBeansOfType(ContainerPortletDescription.class);
    for (ContainerPortletDescription description : allContainerDescriptions.values()) {
      this.containerDescriptions.put(description.getName(), description);
    }
    Map<String, Portlet> allPortlets = this.getApplicationContext().getBeansOfType(AbstractPortlet.class);
    TemplatePortletVisitor templatePortletVisitor = new TemplatePortletVisitor();
    Portlet portlet = null;
    for (String key : allPortlets.keySet()) {
      portlet = allPortlets.get(key);
      portlet.visit(templatePortletVisitor);
      if (templatePortletVisitor.isTemplate()) {
        this.templates.put(key, portlet);
      }
      else {
        this.portlets.put(key, portlet);
      }
      templatePortletVisitor.reset();
    }
    this.modelFactory = new PortletModelFactory(this);

    PortletEditorGUI gui = new PortletEditorGUI();
    PortletEditorController controller = new PortletEditorController(this, gui);
    gui.setController(controller);
    this.setController(controller);
    gui.initialise();

  }



  /**
   * returns all the {@link SimplePortletDescription} to be used by the
   * application, indexed by their names.
   * 
   * @return all the {@link SimplePortletDescription} to be used by the
   *         application, indexed by their names
   */
  @SuppressWarnings("unchecked")
  private Map<String, SimplePortletDescription> getSimplePortletDescriptions()
  {
    Map<String, SimplePortletDescription> res = new HashMap<String, SimplePortletDescription>();

    for (SimplePortletDescription description : ((Map<String, SimplePortletDescription>) this.getApplicationContext().getBeansOfType(SimplePortletDescription.class)).values()) {
      res.put(description.getName(), description);
    }

    for (String controllerName : ((Map<String, Controller>) this.getApplicationContext().getBeansOfType(Controller.class)).keySet()) {
      SimplePortletDescriptionImpl spd = new SimplePortletDescriptionImpl();
      spd.setName(controllerName);
      spd.setAttributeDescription(new ArrayList<VariableDescription>());
      spd.setControllerName(controllerName);
      spd.setDescription(new I18nMessageImpl(Messages.SIMPLE_PORTLET_DESCRIPTION_FOR_CONTROLLER_KEY, controllerName));
      spd.setParameterDescription(new ArrayList<VariableDescription>());
      res.put(controllerName, spd);
    }
    return res;
  }



  /**
   * Close the application.
   */
  @Override
  public void close()
  {
    this.closeAllDocuments();
    super.close();
  }



  /**
   * Create a new document.
   * 
   * @param settings
   *          The document initial settings.
   */
  @Override
  public void createNewDocument(PortletInitialSettings settings)
  {
    if (settings != null) {
      String name = settings.getDocumentName();
      Portlet portlet = this.initialisePortlet(settings);
      if (portlet != null && name != null) {
        DocumentModel document = new DocumentModel(name, this.modelFactory.convert(portlet));
        this.getController().notifyFirstDisplayDocument(document);
      }
    }
  }



  /**
   * Create a new sub-portlet.
   * 
   * @param settings
   *          The document initial settings.
   * @param documentUniqueID
   *          the document unique ID.
   * @param parentUniqueID
   *          the parent unique ID.
   * @param direction
   *          the parent direction.
   */
  @Override
  public void createNewSubElement(PortletInitialSettings settings, UID documentUniqueID, UID parentUniqueID, LayoutDirection direction)
  {
    Portlet portlet = this.initialisePortlet(settings);
    if (portlet != null) {
      PortletModel model = this.modelFactory.convert(portlet);
      model.setDocumentID(documentUniqueID);
      model.setParentID(parentUniqueID);
      model.setSubElement(true);
      model.setParentDirection(direction);
      this.getController().notifyDisplaySubElement(model);
    }
  }



  /**
   * Initialise the portlet from its settings.
   * 
   * @param settings
   *          the settings.
   * @return the portlet.
   */
  protected Portlet initialisePortlet(PortletInitialSettings settings)
  {
    Portlet portlet = null;
    if (settings != null) {
      PortletType type = settings.getPortletType();
      PortletDescription description = settings.getPortletDescription();
      if (PortletType.Container.equals(type)) {
        ContainerPortletDescription containerDescription = (ContainerPortletDescription) description;
        ContainerPortlet containerPortlet = new ContainerPortlet();
        containerPortlet.setViewName(containerDescription.getViewName());
        Map<String, String> attributes = new HashMap<String, String>();
        for (VariableDescription variable : containerDescription.getAttributeDescription()) {
          attributes.put(variable.getName(), "");
        }
        containerPortlet.setAttributes(attributes);
        containerPortlet.setDescriptionName(description.getName());
        portlet = containerPortlet;
      }
      else if (PortletType.Dispatcher.equals(type)) {
        portlet = new DispatcherPortlet();
      }
      else if (PortletType.Linked.equals(type)) {
        portlet = new LinkedPortlet();
      }
      else if (PortletType.Simple.equals(type)) {
        SimplePortletDescription simpleDescription = (SimplePortletDescription) description;
        SimplePortlet simplePortlet = new SimplePortlet();
        simplePortlet.setControllerName(simpleDescription.getControllerName());
        Map<String, String> attributes = new HashMap<String, String>();
        for (VariableDescription variable : simpleDescription.getAttributeDescription()) {
          attributes.put(variable.getName(), "");
        }
        simplePortlet.setAttributes(attributes);
        Map<String, String> parameters = new HashMap<String, String>();
        for (VariableDescription variable : simpleDescription.getParameterDescription()) {
          parameters.put(variable.getName(), "");
        }
        simplePortlet.setParameters(parameters);
        simplePortlet.setDescriptionName(description.getName());
        portlet = simplePortlet;
      }
      else if (PortletType.Hole.equals(type)) {
        portlet = new HolePortlet();
      }
      else if (PortletType.Template.equals(type)) {
        TemplatePortlet templatePortlet = new TemplatePortlet();
        templatePortlet.setLinkedPortletName(settings.getPortletName());
        Portlet initialPortlet = this.getPortletTemplate(settings.getPortletName());
        templatePortlet.setHolesPortlets(this.getHolesFromTemplate(initialPortlet));
        portlet = templatePortlet;
      }
    }
    return portlet;
  }



  /**
   * Select a document from its unique ID.
   * 
   * @param documentUniqueID
   *          the document unique ID.
   */
  public void selectDocument(UID documentUniqueID)
  {
    this.getController().notifyDocumentSelection(documentUniqueID);
  }



  /**
   * Save the document.
   * 
   * @param document
   *          the document.
   */
  @Override
  public void saveDocument(DocumentModel document)
  {
    if (document != null) {
      if (document.getName() == null || document.getName().trim().length() == 0) {
        this.getController().notifyTellUser(this.getTranslation("com.genia.portlet.editor.gui.MainFrame.Ask.Save.Title"),
            this.getTranslation("com.genia.portlet.editor.gui.MainFrame.Ask.Save.Error.name"));
      }
      else {
        this.updateDocumentFromSettings(document);
        if (document.getXml() == null) {
          boolean cancelled = false;
          File xml = null;
          while (!cancelled) {
            xml = this.getController().notifySelectABackupFile();
            if (xml == null) {
              cancelled = !this.getController().notifyAskUser(this.getTranslation("com.genia.portlet.editor.gui.MainFrame.Ask.Save.Title"),
                  this.getTranslation("com.genia.portlet.editor.gui.MainFrame.Ask.Save.Message2", document.getName()));
            }
            else {
              cancelled = true;
            }
          }
          document.setXml(xml);
        }
        if (document.getXml() != null) {
          DocumentModel documentModel = document;
          Portlet portlet = this.modelFactory.convert(documentModel.getElementModel());
          try {
            portlet.visit(new SpringWriterPortletVisitor(new PrintStream(new FileOutputStream(document.getXml())), document.getName()));
            document.setIsSaved(true);
          }
          catch (Exception e) {
            this.getController().notifyTellUser(this.getTranslation("com.genia.portlet.editor.gui.MainFrame.Ask.Save.Title"),
                this.getTranslation("com.genia.portlet.editor.gui.MainFrame.Ask.Save.Error"));
          }
        }
        this.getController().notifyDisplayDocument(document);
      }
    }
  }



  /**
   * Load a document from its xml file.
   * 
   * @param xml
   *          the xml file.
   * @return the loaded document.
   */
  @Override
  @SuppressWarnings("unchecked")
  public DocumentModel loadDocument(File xml)
  {
    DocumentModel document = null;
    try {
      this.trimFileContent(xml);
      ListableBeanFactory beanFactory = new XmlBeanFactory(new FileSystemResource(xml));
      Map<String, Portlet> portlets = beanFactory.getBeansOfType(Portlet.class);
      String name = portlets.keySet().iterator().next();
      Portlet portlet = (Portlet) beanFactory.getBean(name);
      if (portlet != null) {
        PortletModel portletModel = this.modelFactory.convert(portlet);
        document = new DocumentModel(name, xml, portletModel);
        document.setIsSaved(true);
        document.setPortletType(portletModel.getType());
      }
    }
    catch (Exception e) {
      this.getController().notifyTellUser(this.getTranslation("com.genia.portlet.editor.gui.MainFrame.warn.load.title"),
          this.getTranslation("com.genia.portlet.editor.gui.MainFrame.warn.load.message"));
    }
    return document;
  }



  /**
   * Get the simple portlet description.
   * 
   * @param name
   *          the simple portlet description name.
   * @return the simple portlet description.
   */
  public PortletDescription getSimplePortletDescription(String name)
  {
    return this.simpleDescriptions.get(name);
  }



  /**
   * Get the container portlet description.
   * 
   * @param name
   *          the container portlet description name.
   * @return the container portlet description.
   */
  public PortletDescription getContainerPortletDescription(String name)
  {
    return this.containerDescriptions.get(name);
  }



  /**
   * Get the template.
   * 
   * @param templateName
   *          the template name.
   * @return the template.
   */
  public Portlet getPortletTemplate(String templateName)
  {
    Portlet template = (Portlet) this.getApplicationContext().getBean(templateName, Portlet.class);
    return template;
  }



  /**
   * Get the container portlet descriptions.
   * 
   * @return the container portlet descriptions.
   */
  public List<PortletDescription> getContainerPortletsDescription()
  {
    List<PortletDescription> descriptions = new ArrayList<PortletDescription>();
    for (PortletDescription description : this.containerDescriptions.values()) {
      descriptions.add(description);
    }
    Collections.sort(descriptions, new Comparator<PortletDescription>()
    {
      public int compare(PortletDescription o1, PortletDescription o2)
      {
        return o1.getName().compareTo(o2.getName());
      }
    });
    return descriptions;
  }



  /**
   * Get the simple portlet descriptions.
   * 
   * @return the simple portlet descriptions.
   */
  public List<PortletDescription> getSimplePortletsDescription()
  {
    List<PortletDescription> descriptions = new LinkedList<PortletDescription>();
    for (PortletDescription description : this.simpleDescriptions.values()) {
      descriptions.add(description);
    }
    Collections.sort(descriptions, new Comparator<PortletDescription>()
    {
      public int compare(PortletDescription o1, PortletDescription o2)
      {
        return o1.getName().compareTo(o2.getName());
      }
    });
    return descriptions;
  }



  /**
   * Get the templates.
   * 
   * @return the templates.
   */
  public List<String> getTemplatePortlets()
  {
    List<String> templates = new LinkedList<String>();
    for (String name : this.templates.keySet()) {
      templates.add(name);
    }
    Collections.sort(templates);
    return templates;
  }



  /**
   * Get the portlets names.
   * 
   * @return the portlets names.
   */
  public List<String> getPortlets()
  {
    List<String> portlets = new LinkedList<String>();
    for (String name : this.portlets.keySet()) {
      portlets.add(name);
    }
    Collections.sort(portlets);
    return portlets;
  }



  /**
   * Get the holes from a template.
   * 
   * @param template
   *          A template.
   * @return the holes.
   */
  public Map<String, String> getHolesFromTemplate(Portlet template)
  {
    HolesPortletVisitor visitor = new HolesPortletVisitor();
    Map<String, String> holes = new TreeMap<String, String>();
    try {
      template.visit(visitor);
      holes = visitor.getHoles();
    }
    catch (BundledException e) {
      this.getController().notifyTellUser(this.getTranslation("com.genia.portlet.editor.gui.MainFrame.Tell.PortletTemplate.Title"),
          this.getTranslation("com.genia.portlet.editor.gui.MainFrame.Tell.PortletTemplate.Message"));
    }
    return holes;
  }



  /**
   * Launch the application.
   * 
   * @param args
   *          The settings (non needed)
   */
  public static void main(String[] args)
  {
    javax.swing.SwingUtilities.invokeLater(new Runnable()
    {
      public void run()
      {
        PortletEditorApplication application = new PortletEditorApplication();
        application.launch(true);
      }
    });
  }

}
